interface PromiseConstructor {
    delay(ms: number): Promise<void>
    manual<T>(): ManualPromise<T>
    setInterval<T>(generator: () => Promise<T>, timeout?: number): Cancellable;
}

interface Promise<T> {
    delay(ms: number): Promise<T>
}

Promise.prototype.delay = function (ms) {
    return this.then((value) => {
        let token: number;
        let p = new Promise(resolve => {
            token = window.setTimeout(() => resolve(value), ms)
        });
        p.finally(() => window.clearTimeout(token));
        return p;
    })
}

Promise.delay = function (ms) {
    return new Promise<number>(resolve => {
        let a = window.setTimeout(() => resolve(a), ms)
    }).then(window.clearTimeout);
}

Promise.manual = function <T>() {
    return new ManualPromise<T>()
}

class ManualPromise<T> implements Promise<T> {
    private underlyingPromise: Promise<T>

    resolve: (value:T) => void = ()=>{}
    reject: (error:Error) => void = ()=>{}

    constructor() {
        this.underlyingPromise = new Promise<T>(((resolve, reject) => {
            this.resolve = resolve;
            this.reject = reject;
        }))
    }

    get [Symbol.toStringTag](): string {
        return this.underlyingPromise[Symbol.toStringTag]
    }

    catch<TResult = never>(onrejected?: ((reason: any) => (PromiseLike<TResult> | TResult)) | undefined | null): Promise<T | TResult> {
        return this.underlyingPromise.catch(onrejected)
    }

    delay(ms: number): Promise<T> {
        return this.underlyingPromise.delay(ms);
    }

    finally(onfinally?: (() => void) | undefined | null): Promise<T> {
        return this.underlyingPromise.finally(onfinally);
    }

    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => (PromiseLike<TResult1> | TResult1)) | undefined | null, onrejected?: ((reason: any) => (PromiseLike<TResult2> | TResult2)) | undefined | null): Promise<TResult1 | TResult2> {
        return this.underlyingPromise.then(onfulfilled,onrejected);
    }
}

interface Cancellable {
    cancel(): void;
}

Promise.setInterval = <T>(generator: () => Promise<T>, timeout?: number) => {
    let cancelled = false;
    const loopBody = () => {
        Promise.delay(timeout || 0).then(() => {
            if (!cancelled) return generator().finally(loopBody);
        })
    }
    loopBody()
    return { cancel: () => cancelled = true }
}